#!/bin/bash

# gcbs.sh Python3 wrapper for GCBS

SCRIPT_NAME=$(basename $(realpath $0))

# uname -p has portability issue. Example: uname -p returns unknown on debian10 aarch64.
ARCH=$(uname -m)

# version definitions
PYTHON_VERSION=3.7.0
PYPDF2_VERSION=1.26.0
REPORTLAB_VERSION=3.5.53
PILLOW_VERSION=9.1.1
LIBFFI_VERSION=3.4.2
CMAKE_VERSION=3.18.1

# Minimal required cmake version
CMAKE_COMPAT_VERSION=3.14.1

# TOPDIR: GCBS root
TOPDIR=$(realpath $(dirname $(realpath ${BASH_SOURCE[0]})))

# TARDIR: GCBS root / depends
TARDIR=${TOPDIR}/depends

# DISTDIR: where we store python3/cmake binaries.
DISTDIR=${TOPDIR}/buildtools/${ARCH}

# INSTALLDIR: where we extract/install python3/cmake.
INSTALLDIR=${TOPDIR}/buildtools_install/${ARCH}

# Determine whether to use the python/cmake of the software package.
USE_TOOL_PYTHON=0
USE_TOOL_CMAKE=0

[ -z "${GCBSDIR}" ] && GCBSDIR=${TOPDIR}

# unused - cannot find a way not to conflict with GCBS
function usage() {
    cat << EOF >&2
GCBS wrapper script with Python3 shipped
Usage: sh ${SCRIPT_NAME}
GCBS must be installed with this wrapper script, aka. gcbs.py at ${GCBSDIR}/gcbs.py
EOF
    exit 0
}

function exit_with_error() {
    echo -e $1
    exit 1
}

# Currently libffi is available only in build time. it is unclear whether reports can be generated with a ctype missing its libffi dependency.
# At least importing reportlab and generating reports show no error.
function check_python() {
    local python_exe=$(realpath $1)
    cd ${TOPDIR}
    echo -n "Checking for python3....................... "
    [ ! -f ${python_exe} ] && echo -e "no\nError: Python executable file does not exist: ${python_exe}" && return 1
    local version_output=$(${python_exe} --version 2>/dev/null | grep -Po "Python 3[\.0-9]+")
    local ret=$?
    [ ${ret} -ne 0 ] || [ -z "${version_output}" ] && echo -e "no\nError: Python3 version check failed." && return 1
    local version=$(echo ${version_output} | awk '{print $NF}' | grep '3.[0-9+]')
    [ -z "${version}" ] && echo -e "no\nError: Unable to detect python3 version." && return 1
    echo "yes, Version: ${version}"

    echo -n "Checking for python package dependencies... "
    cat << EOF > check_gcbs_dependencies.py
import reportlab
import PyPDF2
import PIL
import os
from reportlab.pdfgen.canvas import Canvas
canvas = Canvas("")
logo_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "framework", "report", "logo.png")
canvas.drawImage(logo_dir, 0, 0)
EOF
    $(${python_exe} check_gcbs_dependencies.py 1>/dev/null 2>&1)
    local ret=$?
    if [ ${ret} -eq 0 ]; then
        echo "yes"
    else
        echo "no"
        rm check_gcbs_dependencies.py
        return 1
    fi
    rm check_gcbs_dependencies.py
    echo "             package : reportlab........... yes"
    echo "             package : PyPDF2.............. yes"
    echo "             package : Pillow.............. yes"
    #md5sum ${python_exe} | awk '{print $1}' > $(dirname ${python_exe})/../VERIFIED_PYTHON
    USE_TOOL_PYTHON=1
    return 0
}

# If distribution itself is broken, this script would not try to make a new one and has to exit.
function deploy_python_distribution() {
    echo -n "Checking for buildin Python3 archive....... "
    local python_dist=${DISTDIR}/Python-${PYTHON_VERSION}_${ARCH}.tar.xz
    if [ -f "${python_dist}" ]; then
        echo "yes"
        mkdir -p ${INSTALLDIR}
        #echo "Extracting ${python_dist}"
        tar -xf ${python_dist} -C ${INSTALLDIR}
        #echo "Extraction done."
    else
        echo "no"
        echo -e "No builtin Python3 is available for this architecture."
        return 1
    fi

    export_lib_path && check_python ${INSTALLDIR}/Python-${PYTHON_VERSION}/bin/python3 || echo -e "Error: Exception in checking Python3." && return 1
}

# Warning: rm -rf used. As INSTALLDIR is managed by this script and unmodifiable from outside, we assume rm -rf is safe here.
function check_python_dist_with_fallback() {
    export_lib_path && check_python "${INSTALLDIR}/Python-${PYTHON_VERSION}/bin/python3" || {
        echo "Error: Exception in checking, falling back to archived python." && rm -rf "${INSTALLDIR}/Python-${PYTHON_VERSION}" && deploy_python_distribution
        }
}

# before checking the Python in the tool, add the lib path to LD_LIBRARY_PATH.
function export_lib_path(){
    LD_LIBRARY_PATH=${INSTALLDIR}/Python-${PYTHON_VERSION}/lib:${LD_LIBRARY_PATH}
    export LD_LIBRARY_PATH
}

# version check <, return 0 for True
function version_lt() {
    local version_a=$1
    local version_b=$2
    if [ ! -z ${version_a} ] && [ ! -z ${version_b} ]; then
        smaller=$(echo -e "${version_a}\n${version_b}" | sort -V | head -n 1)
        [ ${version_a} = ${smaller} ] && [ ! ${version_a} = ${version_b} ] && return 0
        return 1
    else
        exit_with_error "Error: Illegal argument for version checking."
    fi
}

# cmake checking, we use version which does not suffice but work.
function check_cmake() {
    local cmake_exe=$(realpath $1)
    echo -n "Checking for cmake......................... "
    [ ! -f ${cmake_exe} ] && echo -e "no\nError: CMake executable does not exist: ${cmake_exe}" && return 1
    local version_output=$(${cmake_exe} --version 2>/dev/null | head -n 1 | grep -Po "cmake version [\.0-9]+")
    local ret=$?
    [ ${ret} -ne 0 ] || [ -z "${version_output}" ] && echo -e "no\nError: CMake version check failed." && return 1
    local version=$(echo ${version_output} | awk '{print $NF}')
    [ -z "${version}" ] && echo -e "no\nError: Unable to detect CMake version " && return 1
    version_lt ${version} ${CMAKE_COMPAT_VERSION} && echo -e "no\nError: CMake too old: ${version}. Expected: >= ${CMAKE_COMPAT_VERSION}" && return 1
    echo "yes, Version: ${version}"
    USE_TOOL_CMAKE=1
    return 0
}

function deploy_cmake_distribution() {
    echo -n "Checking for buildin CMake archive......... "
    local cmake_dist=${DISTDIR}/CMake-${CMAKE_VERSION}_${ARCH}.tar.xz
    if [ -f "${cmake_dist}" ]; then
        echo "yes"
        mkdir -p ${INSTALLDIR}
        #echo "Extracting ${cmake_dist}"
        tar -xf ${cmake_dist} -C ${INSTALLDIR}
        #echo "Extraction done."
    else
        echo "no"
        echo -e "No builtin CMake is available for this architecture."
        return 1
    fi
    check_cmake ${INSTALLDIR}/CMake-${CMAKE_VERSION}/bin/cmake || echo -e "Error: Exception in checking CMake." && return 1
}

function check_cmake_dist_with_fallback() {
    check_cmake "${INSTALLDIR}/CMake-${CMAKE_VERSION}/bin/cmake" || {
        echo "Error: Exception in checking, falling back to archived cmake." && rm -rf "${INSTALLDIR}/CMake-${CMAKE_VERSION}" && deploy_cmake_distribution
        }
}

function preflight_gcbs() {
    cd ${TOPDIR}
    mkdir -p ${DISTDIR} ${INSTALLDIR}
    
    if [ -f "${INSTALLDIR}/Python-${PYTHON_VERSION}/bin/python3" ]; then
        check_python_dist_with_fallback
    else
        deploy_python_distribution
    fi

    # this is a temporary fix as GCBS calls python3 which is problematic - python3 must be in PATH
    local system_python=$(which python3 2>/dev/null)
    if [ ${USE_TOOL_PYTHON} -eq 1 ]; then
            PATH=${INSTALLDIR}/Python-${PYTHON_VERSION}/bin:${PATH}
        else
            if [ ! -z ${system_python} ] && check_python ${system_python} > /dev/null; then
                    echo "Checking for system python... yes"
                else
                    exit_with_error "Error: no satisfied python."
            fi
    fi
    
    # we make an initial check on user cmake, if not usable, we fall back to pre-built cmake archives.
    if [ -f "${INSTALLDIR}/CMake-${CMAKE_VERSION}/bin/cmake" ]; then
        check_cmake_dist_with_fallback
    else
        deploy_cmake_distribution
    fi

    # adding pre-built cmake to PATH if applicable
    local system_cmake=$(which cmake 2>/dev/null)
    if [ ${USE_TOOL_CMAKE} -eq 1 ]; then
            PATH=${INSTALLDIR}/CMake-${CMAKE_VERSION}/bin:${PATH}
        else
            if [ ! -z ${system_cmake} ] && check_cmake ${system_cmake} > /dev/null; then
                    echo "Checking for system cmake... yes"
                else
                    exit_with_error "Error: no satisfied cmake."
            fi
    fi
    
    export PATH

    #echo "Goto GCBS ..."

    [ ! -f "${GCBSDIR}/framework/gcbs.py" ] &&
    exit_with_error "Error: GCBS not found! ${GCBSDIR}/gcbs.py does not exist. Please set GCBSDIR variable. Abort."
}

function main() {
    preflight_gcbs
    # **Security hole alert: gcbs.py is not managed by this script, calling directly with root privileges is problematic.
    cd ${GCBSDIR}
    exec python3 ${GCBSDIR}/framework/gcbs.py $@
}

[ -z ${SOURCE_FROM_BUILDER} ] && main $@